<?php
/*
* @file 
*      all the functions that might be needed to build a form
*/

/**
 * form_build()
 * 
 * This function creates a form from an array of attributes and inputs stored as arrays themselves
 * and returns the HTML output
 * 
 * @param mixed $form
 * 		The array representing the form
 * 
 * @param mixed $form_id
 * 		A unique ID for the form--used for verification and submission purposes
 * 
 * @return
 * 		HTML output of the form
 */
function form_build(&$form, $form_id)
{
	$form['#type'] = 'form';
	
	// add a hidden form_id field for validation and also to manage directing the form submission
	if (!isset($form['#form_id']))
	{
		$form['#form_id'] = $form_id;

		$form['#inputs']['form_id'] = array('#type' => 'hidden', '#value' => $form['#form_id'], '#id' => render_unique_id($form['#form_id']), '#name' => 'form_id');
	}
	
	// a hidden input that handles an form validation
	if (isset($form['#validator']) && !isset($form['#inputs']['validator']))
	{
		 $form['#inputs']['validator'] = array('#type' => 'hidden', '#value' => $form['#validator'], '#id' => render_unique_id($form['#validator']), '#name' => 'validator');
	}
	
	// another hidden input to directly handle form submission
	if (isset($form['#callback']) && !isset($form['#inputs']['callback']))
	{
		 $form['#inputs']['callback'] = array('#type' => 'hidden', '#value' => $form['#callback'], '#id' => render_unique_id($form['#callback']), '#name' => 'callback');
	}
	
	// make sure that the form has a unique ID
	if (!isset($form['#id']))
	{
		$form['#id'] = render_unique_id($form_id);
	}
	
	form_fill_element_properties($form);
	
	// set the render function name so the render() function will know what to call
	$form['#render'] = 'form';
	
	// fill in any defaults for the form's inputs
	$form = form_prepare_form_element($form);
	
	// store the form_id for verification
	$_SESSION['forms'][$form['#form_id']] = $form;
	
	return form_compile($form);
}

/**
 * form_prepare_form_element()
 * 
 * This function prepares a form element to be rendered by adding any necessary properties like name, id, etc,
 * and it acts recursively so calling this function on the original form array will call this function on the form's
 * inputs too
 * 
 * @param mixed $element
 * @return
 */
function form_prepare_form_element($element)
{
	// check to see if any inputs need defaults filled, etc.
	if (empty($element['#processed']))
	{
		form_fill_element_properties($element);
		$element['#processed'] = true;
	}

	// set every element's id
	if (!isset($element['#id']))
	{
		$element['#id'] = render_unique_id($element['#name']);
	}

	$element['#sorted'] = true;
	
	// only gets called for forms because for every form $form['#inputs'] is where the form inputs are located
	if (isset($element['#inputs']))
	{
		$count = 0;
		foreach ($element['#inputs'] as $key => $value)
		{
			if (empty($value['#processed']))
			{
				form_fill_element_properties($value);
				$value['#processed'] = true;
			}

			if (!empty($value['#input']))
			{
				if (!isset($value['#name']))
				{
					$value['#name'] = $key;
				}
			}

			if (!isset($value['#order']))
			{
				$value['#order'] = $count;
			}
			else
			{
				unset($element['#sorted']);
			}

			$element['#inputs'][$key] = form_prepare_form_element($value);
			$count++;
		}
	}

	return $element;
}

/**
 * form_compile()
 * 
 * Renders each form input and returns the HTML
 * 
 * @param mixed $elements
 * @return
 */
function form_compile(&$elements)
{
	if (!isset($elements))
	{
		return NULL;
	}
	
	if (!isset($elements['#processed']) || !$elements['#processed'])
	{
		form_fill_element_properties($elements);
	}
	
	$output = '';
	if (!isset($elements['#content']))
	{
		$elements['#content'] = '';
		
		// returns any input children that an input might have
		$children = system_element_children($elements);
		
		// if an element has a render function but hasn't been rendered, then render it
		if (isset($elements['#render']) && empty($elements['#rendered']))
		{
			$elements['#rendered'] = TRUE;
			
			// element is a single element, like a button
			if (empty($children))
			{
				$elements['#printed'] = TRUE;
				$elements['#content'] = _form_compile($elements);
				$output = $elements['#content'];
			}
			else
			{
				// element is probably a form
				foreach ($children as $key => $value) 
				{					
					$elements['#content'] .= form_compile($value);
					$elements['#inputs'][$key] = $value;
				}
			}
		}
	}

	// Until now, we rendered the children, here we form_compile the element itself
	if (!isset($elements['#printed']))
	{
		$output = _form_compile($elements);
		$elements['#printed'] = TRUE;
	}
	
	// check for any HTML prefixes/suffixes
	$prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
	$suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
	$output = $prefix . $output . $suffix;
	
	return $output;
}

/**
 * _form_compile()
 * 
 * form_compile() Helper function
 * 
 * If an $element doesn't have a #render property stored, then make one
 * 
 * @param mixed $element
 * @return
 */
function _form_compile($element)
{
	$output = '';
	
	$function = isset($element['#render']) ? 'render_' . $element['#render'] : 'render_' . $element['#type'];
	if (function_exists($function))
	{
		$output = $function($element);
	}
	
	return $output;
}

/**
 * form_fill_element_properties()
 * 
 * Fill in basic form elements that might be missing
 * 
 * @param mixed $element
 * @return
 */
function form_fill_element_properties(&$element)
{
	$form_defaults = array('#description' => null, '#attributes' => array(), '#required' => false, '#render' => $element['#type']);

	$element += $form_defaults;

	if (isset($element['#type']) && $element['#type'] != 'form')
	{
		$element['#input'] = true;
	}
}

/**
 * form_process_submitted_form()
 * 
 * This function handles the verification of forms (ie. that they came from who they say they came from, etc.),
 * the handling of validation, and the routing of form submission if validation passes or was not specified
 * 
 * @param mixed $post
 * @return
 */
function form_process_submitted_form($post)
{
	// store the original result just in case
	$form_result = array('#post' => $post);
	
	// if there is no record of this form being processed by the user
	// return nothing and do nothing
	if (empty($_SESSION['forms'][$post['form_id']]))
	{
		return array();
	}
	
	// retrieve the original form array as stored in the user's session
	$stored_form = $_SESSION['forms'][$post['form_id']];
	
	// check the form's id
	if (!empty($post['form_id']) && $post['form_id'] == $stored_form['#form_id'])
	{
		$form_result['form_id'] = $post['form_id'];
		unset($post['form_id']);
		
		// check to see if there is a validator for this form
		$validator = FALSE;
		if (isset($post['validator']) && isset($stored_form['#validator']) && $post['validator'] == $stored_form['#validator'])
		{
			$form_result['#validator'] = $post['validator'];
			unset($post['validator']);
			$validator = TRUE;
		}
		else 
		{
			$validator_function = $form_result['form_id'];
			if (function_exists($validator_function))
			{
				$form_result['#validator'] = $validator_function;
				$validator = TRUE;
			}
		}
		
		// look for any callback functions
		$callback = FALSE;
		if (isset($post['callback']) && isset($stored_form['#callback']) && $post['callback'] == $stored_form['#callback'])
		{
			$form_result['#callback'] = $post['callback'];
			unset($post['callback']);
			$callback = TRUE;
		}
		else 
		{
			$callback_function = $form_result['form_id'] . '_submit';
			if (function_exists($callback_function))
			{
				$form_result['#callback'] = $callback_function;
				$callback = TRUE;
			}
		}
		
		// store the submitted variables in a universal location so that form validators and callback functions
		// do not need to look for them
		$form_result['values'] = array();
		foreach ($post as $key => $form_value) 
		{
			$form_result['values'][$key] = $form_value; 
		}
		
		// if one exists, then call it.
		// if any of the validators finds a problem with a submission (ie. invalid email address or something),
		// the validator just calls the form_error function and adds an error message for that specific input 
		if ($validator)
		{
			call_user_func_array($form_result['#validator'], array(&$form_result));
		}
		
		// if one exists, then call it. 
		// the callback function is only called if the validator did not set any errors
		if ($callback && !form_get_errors())
		{
			call_user_func_array($form_result['#callback'], array(&$form_result));
		}
	}
	
	return $form_result;
}

/**
 * form_error()
 * 
 * Allows getting and setting of any form errors
 * 
 * @param mixed $name
 * 		the name of the input that generated the error
 * 
 * @param string $error
 * 		the error message that goes with the error
 *  
 * @return
 * 		The entire array of errors
 */
function form_error($name = NULL, $error = '')
{
	static $errors = array();
	
	if (isset($name))
	{
		if (!empty($error))
		{
			$errors[$name] = $error;
			system_set_message($error, 'error');
		}
		else if (isset($errors[$name]))
		{
			return $errors[$name];
		}
		else 
		{
			return FALSE;
		}
	}
	
	return $errors;
}

/**
 * form_get_errors()
 * 
 * Checks for form errors, and if any exist, then return them; otherwise, return FALSE;
 * 
 * @return
 */
function form_get_errors()
{
	$errors = form_error();
	
	if (empty($errors))
	{
		return FALSE;
	}
	
	return $errors;
}

/**
 * render_form_element()
 * 
 * This function renders the HTML <div> containers for most form elements,
 * and includes <label> elements and description <div> elements
 * 
 * @param mixed $element
 * @param mixed $value
 * @return
 */
function render_form_element($element, $value)
{
	$output = '<div class="form-item ' . render_html_class($element['#type']) . '"';
	if (!empty($element['#id']))
	{
		$output .= ' id="' . $element['#id'] . '-wrapper"';
	}

	$output .= ">\n";

	$required = !empty($element['#required']) ? '<span class="form-required" title="' . $t('This field is required.') . '">*</span>' : '';

	if (!empty($element['#title']))
	{
		$title = $element['#title'];
		if (!empty($element['#id']))
		{
			$output .= ' <label for="' . $element['#id'] . '">' . $title . ': ' . $required . "</label>\n";
		}
		else
		{
			$output .= ' <label>' . $title . ': ' . $required . "</label>\n";
		}
	}

	$output .= $value . "\n";

	if (!empty($element['#description']))
	{
		if (isset($element['#type']))
		{
			$output .= ' <div class="description small field-' . render_html_class($element['#type']) . '">' . $element['#description'] . "</div>\n";
		}
		else
		{
			$output .= ' <div class="description small">' . $element['#description'] . "</div>\n";
		}
	}

	$output .= "</div>\n";

	return $output;
}

/**
 * form_add_html_classes()
 * 
 * This is a helper function that adds an array() of classes to an element array()
 * 
 * @param mixed $element
 * @param mixed $classes
 * @return
 */
function form_add_html_classes(&$element, $classes = array())
{
	if (!empty($element['#attributes']['class']))
	{
		$classes[] = $element['#attributes']['class'];
	}
	
	if (form_error($element['#name']) && $element['#type'] != 'select')
	{
		$classes[] = 'error';
	}
    
	$element['#attributes']['class'] = implode(' ', $classes);
}

/**
 * render_html()
 * 
 * This function renders and basic HTML output, ie for adding <div> wrappers to form elements, etc.
 * 
 * @param mixed $element
 * @return
 */
function render_html($element)
{
	return (isset($element['#value']) ? $element['#value'] : '');
}

/**
 * render_submit()
 * 
 * This function renders an HTML submit button
 * 
 * @param mixed $element
 * @return
 */
function render_submit($element)
{
	$element['#button_type'] = 'submit';
	$element['#name'] = 'submit';
	return render_button($element);
}

/**
 * render_button()
 * 
 * This function renders an HTML button
 * 
 * @param mixed $element
 * @return
 */
function render_button($element)
{
	if (empty($element['#button_type']))
	{
		$element['#button_type'] = 'button';
	}

	if (isset($element['#attributes']['class']))
	{
		$element['#attributes']['class'] = 'form-' . $element['#button_type'] . ' ' . $element['#attributes']['class'];
	}
	else
	{
		$element['#attributes']['class'] = 'form-' . $element['#button_type'];
	}

	return render_form_element($element, '<input type="submit" ' . (empty($element['#name']) ? '' : 'name="' . $element['#name'] . '" ') . ' id="' . $element['#id'] . '" value="' . $element['#value'] . '" ' . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . " />\n");
}

/**
 * render_hidden()
 * 
 * @param mixed $element
 * @return
 */
function render_hidden($element)
{
	return '<input type="hidden" name="' . $element['#name'] . '" id="' . $element['#id'] . '" value="' . html_safe($element['#value']) . '"' . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . " />\n";
}

/**
 * render_textfield()
 * 
 * This function renders an HTML textfield
 * 
 * @param mixed $element
 * @return
 */
function render_textfield($element)
{
	$size = empty($element['#size']) ? '' : ' size="' . $element['#size'] . '"';
	$maxlength = empty($element['#maxlength']) ? '' : ' maxlength="' . $element['#maxlength'] . '"';

	$output = '';

	if (isset($element['#field_prefix']))
	{
		$output .= '<span class="field-prefix">' . $element['#field_prefix'] . '</span>';
	}

	form_add_html_classes($element, array('form-text'));

	$output .= '<input type="text"' . $maxlength . ' name="' . $element['#name'] . '" id="' . $element['#id'] . '"' . $size . ' value="' . (isset($element['#value']) ? html_safe($element['#value']) : '') . '"' . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . ' />';

	if (isset($element['#field_suffix']))
	{
		$output .= '<span class="field-suffix">' . $element['#field_suffix'] . '</span>';
	}

	return render_form_element($element, $output);
}

/**
 * render_textarea()
 * 
 * This function renders an HTML textarea field
 * 
 * @param mixed $element
 * @return
 */
function render_textarea($element)
{
	form_add_html_classes($element, array('form-textarea'));

	return render_form_element($element, '<textarea cols="' . $element['#cols'] . '" rows="' . $element['#rows'] . '" name="' . $element['#name'] . '" id="' . $element['#id'] . '" ' . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . '>' . (empty($element['#value']) ? '' : html_safe($element['#value'])) . "</textarea>\n");
}

/**
 * render_password()
 * 
 * This function renders an HTML password field
 * 
 * @param mixed $element
 * @return
 */
function render_password($element)
{
	$size = isset($element['#size']) ? ' size="' . $element['#size'] . '" ' : '';
	$maxlength = isset($element['#maxlength']) ? ' maxlength="' . $element['#maxlength'] . '" ' : '';

	form_add_html_classes($element, array('form-password'));

	$output = '<input type="password" name="' . $element['#name'] . '" id="' . $element['#id'] . '" ' . $maxlength . $size . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . " />\n";
	return render_form_element($element, $output);
}

/**
 * render_checkbox()
 * 
 * This function renders an HTML form checkbox
 * 
 * @param mixed $element
 * @return
 */
function render_checkbox($element)
{
	form_add_html_classes($element, array('form-checkbox'));
	$output = '<input type="checkbox" id="' . $element['#id'] . '" name="' . $element['#name'] . '" value="' . $element['#value'] . '" ' . ($element['#checked'] === TRUE ? 'checked="checked"' : '') . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . " />\n";

	if (!empty($element['#title']))
	{
		$output .= '<label class="option" for="' . $element['#id'] . '">' . $element['#title'] . "</label>\n";
		unset($element['#title']);
	}

	return render_form_element($element, $output);
}

/**
 * render_select()
 * 
 * This function renders a selection form element
 * 
 * @param mixed $element
 * @return
 */
function render_select($element)
{
	$size = isset($element['#size']) ? ' size="' . $element['#size'] . '"' : '';
	form_add_html_classes($element, array('form-select'));
	$multiple = isset($element['#multiple']) ? $element['#multiple'] : NULL;

	return render_form_element($element, '<select name="' . $element['#name'] . ($multiple ? '[]' : '') . '" ' . ($multiple ? 'multiple="multiple"' : '') . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . ' id="' . $element['#id'] . '" ' . $size . '>' . render_select_options($element) . '</select>');
}

/** 
 * render_select_options()
 * 
 * @param mixed $element
 * @return
 */
function render_select_options($element)
{
	$opts_list = $element['#options'];
    
    $output = '';
    foreach ($opts_list as $key => $option) 
    {
    	$selected = (!empty($option['#selected']) && $option['#selected'] === TRUE ? ' selected="selected"' : '');    	
    	$output .= '<option value="' . html_safe($option['#value']) . '"' . (empty($option['#attributes']) ? '' : render_attributes($option['#attributes'])) . $selected . '>' . $option['#title'] . '</option>' . "\n";
    }
    
    return $output;
}

/**
 * render_form()
 * 
 * This function creates an HTML form element
 * 
 * @param mixed $element
 * @return
 */
function render_form($element)
{
	$action = $element['#action'] ? 'action="' . $element['#action'] . '" ' : '';
	return '<form ' . $action . ' accept-charset="UTF-8" method="' . $element['#method'] . '" id="' . $element['#id'] . '"' . (empty($element['#attributes']) ? '' : render_attributes($element['#attributes'])) . ">\n" . '<div class="form-wrapper">' . $element['#content'] . "\n</div></form>\n";
}
